#include "D3DObj.h"
#include "ReportError.h"

D3DObj::D3DObj()
{
	m_Device = 0;
	m_SwapChain = 0;

	m_DepthStencilBuffer = 0;
	m_RenderTargetView = 0;
	m_DepthStencilView = 0;

	m_DepthStencilState = 0;
	m_RasterState = 0;
}

D3DObj::D3DObj(const D3DObj& other)
{
}

D3DObj::~D3DObj()
{
}

bool D3DObj::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, 
	float screenDepth, float screenNear)
{
	//Store the width and height
	m_ScreenWidth = screenWidth;
	m_ScreenHeight = screenHeight;

	//Need to store for V_Sync
	int numerator = 0;
	int denominator = 1;	
	
	// Store the vsync setting.
	m_vsync_enabled = vsync;


	//Calculate the refresh rate from the video card/monitor
	if(vsync)
	{
		CalculateRefreshRate(numerator, denominator);
	}

	//Create the description of the swap chain.
	if(!CreateSwapChain(numerator, denominator, hwnd, fullscreen))
	{
		//MessageBox(0, "D3D Init Error: Unable To Create SwapChain", 0,0);
		ReportError("D3D Init Error: Unable To Create SwapChain");
		PostQuitMessage(0);
	}
	
	//Now that we have a device and swap chain we need to get a pointer to the 
	//back buffer and then attach it to the swap chain. We'll use the CreateRenderTargetView 
	//function to attach the back buffer to our swap chain.

	HRESULT result;

	// Get the pointer to the back buffer.
	ID3D10Texture2D* backBufferPtr;
	result = m_SwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&backBufferPtr);
	if(FAILED(result))
	{
		return false;
	}

	// Create the render target view with the back buffer pointer.
	result = m_Device->CreateRenderTargetView(backBufferPtr, NULL, &m_RenderTargetView);
	if(FAILED(result))
	{
		//MessageBox(0, "D3D Init Error: Unable To Create RenderTargetView", 0,0);
		ReportError("D3D Init Error: Unable To Create RenderTargetView");
		PostQuitMessage(0);
		return false;
	}

	//Release pointer to the back buffer as we no longer need it.
	backBufferPtr->Release();
	backBufferPtr = 0;

	//set up a depth buffer description and attach a stencil buffer
	if(!CreateDepthBuffer())
	{
		//MessageBox(0, "D3D Init Error: Unable To Create DepthBuffer", 0,0);
		ReportError("D3D Init Error: Unable To Create DepthBuffer");
		PostQuitMessage(0);
		return false;
	}

	//setup the depth stencil description
	if(!CreateStencilBuffer())
	{
		//MessageBox(0, "D3D Init Error: Unable To Create StencilBuffer", 0,0);
		ReportError("D3D Init Error: Unable To Create StencilBuffer");
		PostQuitMessage(0);
		return false;
	}

	// Bind the render target view and depth stencil buffer to the output render pipeline.
	m_Device->OMSetRenderTargets(1, &m_RenderTargetView, m_DepthStencilView);

	//create rasterizer state
	if(!CreateRasterState())
	{
		//MessageBox(0, "D3D Init Error: Unable To Create RasterState", 0,0);
		ReportError("D3D Init Error: Unable To Create RasterState");
		PostQuitMessage(0);
		return false;
	}
	
	//The viewport also needs to be setup so that Direct3D can map clip space coordinates 
	//to the render target space. Set this to be the entire size of the window.

	D3D10_VIEWPORT viewport;
	// Setup the viewport for rendering.
	viewport.Width = screenWidth;
	viewport.Height = screenHeight;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;

	// Create the viewport.
	m_Device->RSSetViewports(1, &viewport);

	return true;
}

bool D3DObj::OnResize()
{
	// Release the old views, as they hold references to the buffers we
	// will be destroying.  Also release the old depth/stencil buffer.

	if(m_RenderTargetView)
	m_RenderTargetView->Release();

	if(m_DepthStencilView)
	m_DepthStencilView->Release();

	if(m_DepthStencilBuffer)
	m_DepthStencilBuffer->Release();


	// Resize the swap chain and recreate the render target view.

	m_SwapChain->ResizeBuffers(1, m_ScreenWidth, m_ScreenHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
	
	//ID3D10Texture2D* backBuffer;
	//m_SwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), reinterpret_cast<void**>(&backBuffer));
	//m_Device->CreateRenderTargetView(backBuffer, 0, &m_RenderTargetView);

	//if(backBuffer)
	//backBuffer->Release();

	//// Create the depth/stencil buffer and view.
	//D3D10_TEXTURE2D_DESC depthStencilDesc;
	//
	//depthStencilDesc.Width     = m_ScreenWidth;
	//depthStencilDesc.Height    = m_ScreenHeight;
	//depthStencilDesc.MipLevels = 1;
	//depthStencilDesc.ArraySize = 1;
	//depthStencilDesc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;
	//depthStencilDesc.SampleDesc.Count   = 1; // multisampling must match
	//depthStencilDesc.SampleDesc.Quality = 0; // swap chain values.
	//depthStencilDesc.Usage          = D3D10_USAGE_DEFAULT;
	//depthStencilDesc.BindFlags      = D3D10_BIND_DEPTH_STENCIL;
	//depthStencilDesc.CPUAccessFlags = 0; 
	//depthStencilDesc.MiscFlags      = 0;

	//m_Device->CreateTexture2D(&depthStencilDesc, 0, &m_DepthStencilBuffer);


	//m_Device->CreateDepthStencilView(m_DepthStencilBuffer, 0, &m_DepthStencilView);


	//// Bind the render target view and depth/stencil view to the pipeline.
	//m_Device->OMSetRenderTargets(1, &m_RenderTargetView, m_DepthStencilView);

	HRESULT result;

	// Get the pointer to the back buffer.
	ID3D10Texture2D* backBufferPtr;
	result = m_SwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&backBufferPtr);
	if(FAILED(result))
	{
		//MessageBox(0, "D3D Init Error: Unable To Access BackBuffer", 0,0);
		ReportError("D3D Init Error: Unable To Access BackBuffer");
		PostQuitMessage(0);
		return false;
	}

	// Create the render target view with the back buffer pointer.
	result = m_Device->CreateRenderTargetView(backBufferPtr, NULL, &m_RenderTargetView);
	if(FAILED(result))
	{
		//MessageBox(0, "D3D Init Error: Unable To Create RenderTargetView", 0,0);
		ReportError("D3D Init Error: Unable To Create RenderTargetView");
		PostQuitMessage(0);
		return false;
	}

	//Release pointer to the back buffer as we no longer need it.
	backBufferPtr->Release();
	backBufferPtr = 0;

	//set up a depth buffer description and attach a stencil buffer with the new window size
	if(!CreateDepthBuffer())
	{
		//MessageBox(0, "D3D Init Error: Unable To Create DepthBuffer", 0,0);
		ReportError("D3D Init Error: Unable To Create DepthBuffer");
		PostQuitMessage(0);
		return false;
	}


	//TODO: May not need to recreat this
	//setup the depth stencil description with the new window size
	if(!CreateStencilBuffer())
	{
		//MessageBox(0, "D3D Init Error: Unable To Create StencilBuffer", 0,0);
		ReportError("D3D Init Error: Unable To Create StencilBuffer");
		PostQuitMessage(0);
		return false;
	}

	// Bind the new render target view and depth stencil buffer to the output render pipeline
	m_Device->OMSetRenderTargets(1, &m_RenderTargetView, m_DepthStencilView);
	

	// Set the viewport transform
	D3D10_VIEWPORT vp;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	vp.Width    = m_ScreenWidth;
	vp.Height   = m_ScreenHeight;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;

	m_Device->RSSetViewports(1, &vp);

	return true;
}

bool D3DObj::CalculateRefreshRate(int& Numerator, int& Denominator)
{
	//Calculate the refresh rate from the video card/monitor. 
	
	//query for the numerator and denominator values pass them to DirectX it will 
	//calculate the proper refresh rate. 
	
	//If we don't do this and just set the refresh rate to a default value which may 
	//not exist on all computers then DirectX will respond by performing a blit instead 
	//of a buffer flip which will degrade performance and give us annoying errors in the debug output.

	HRESULT result;

	// Create a DirectX graphics interface factory.
	IDXGIFactory* factory;
	result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
	if(FAILED(result))
	{
		return false;
	}

	// Use the factory to create an adapter for the primary graphics interface (video card)
	IDXGIAdapter* adapter;
	result = factory->EnumAdapters(0, &adapter);
	if(FAILED(result))
	{
		return false;
	}

	// Enumerate the primary adapter output (monitor)
	IDXGIOutput* adapterOutput;
	result = adapter->EnumOutputs(0, &adapterOutput);
	if(FAILED(result))
	{
		return false;
	}

	// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor)
	unsigned int numModes;

	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Create a list to hold all the possible display modes for this monitor/video card combination
	DXGI_MODE_DESC* displayModeList;
	
	displayModeList = new DXGI_MODE_DESC[numModes];
	if(!displayModeList)
	{
		return false;
	}

	//fill the display mode list structures
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
	if(FAILED(result))
	{
		return false;
	}

	// Now go through all the and find the display mode one that matches the screen width and height

	for(unsigned int i = 0; i < numModes; i++)
	{
		if(displayModeList[i].Width == (unsigned int)m_ScreenWidth)
		{
			if(displayModeList[i].Height == (unsigned int)m_ScreenHeight)
			{
				// When a match is found store the numerator and 
				//denominator of the refresh rate for that monitor
				Numerator = displayModeList[i].RefreshRate.Numerator;
				Denominator = displayModeList[i].RefreshRate.Denominator;
			}
		}
	}

	//retrieve the name of the video card and the amount of 
	//memory on the video card using the adapter

	// Get the adapter (video card) description
	DXGI_ADAPTER_DESC adapterDesc;
	result = adapter->GetDesc(&adapterDesc);
	if(FAILED(result))
	{
		return false;
	}

	// Store the dedicated video card memory in megabytes
	m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

	unsigned int stringLength;
	int error;
	// Convert the name of the video card to a character array and store it.
	error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
	if(error != 0)
	{
		return false;
	}

	//release the structures and interfaces used to get that info

	// Release the display mode list
	delete [] displayModeList;
	displayModeList = 0;

	// Release the adapter output
	adapterOutput->Release();
	adapterOutput = 0;

	// Release the adapter
	adapter->Release();
	adapter = 0;

	// Release the factory
	factory->Release();
	factory = 0;

	return true;
}

bool D3DObj::CreateSwapChain(int Numerator, int Denominator, HWND hwnd, bool fullscreen)
{
	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	// Initialize the swap chain description.
	ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

	// Set to a single back buffer.
	swapChainDesc.BufferCount = 1;

	// Set the width and height of the back buffer.
	swapChainDesc.BufferDesc.Width = m_ScreenWidth;
	swapChainDesc.BufferDesc.Height = m_ScreenHeight;

	// Set regular 32-bit surface for the back buffer.
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

	//If vsync is set to true lock the refresh rate to the system settings (for example 60hz). 
	//if we set vsync to false then it will draw the screen as many times a second as it can, 
	//however this can cause some visual artifacts.

	// Set the refresh rate of the back buffer.
	if(m_vsync_enabled)
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = Numerator;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = Denominator;
	}
	else
	{
		swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
		swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	}

	// Set the usage of the back buffer
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

	// Set the handle for the window to render to
	swapChainDesc.OutputWindow = hwnd;

	// Turn multisampling off
	swapChainDesc.SampleDesc.Count = 1;
	swapChainDesc.SampleDesc.Quality = 0;

	// Set to full screen or windowed mode
	if(fullscreen)
	{
		swapChainDesc.Windowed = false;
	}
	else
	{
		swapChainDesc.Windowed = true;
	}

	// Set the scan line ordering and scaling to unspecified
	swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

	// Discard the back buffer contents after presenting
	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

	// Don't set the advanced flags
	swapChainDesc.Flags = 0;

	//Now that the swap chain description has been filled out we can create it 
	//as well as the Direct3D device. The Direct3D device is very important, it 
	//is our interface to calling all of the Direct3D functions. We will use the 
	//device for almost everything from this point forward.

	HRESULT result;

	// Create the swap chain and the Direct3D device.
	result = D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, 0, D3D10_SDK_VERSION, 
	&swapChainDesc, &m_SwapChain, &m_Device);

	if(FAILED(result))
	{
		return false;
	}
	return true;
}

bool D3DObj::CreateDepthBuffer()
{
	//We will also need to set up a depth buffer description. We'll use this to create 
	//a depth buffer so that our polygons can be rendered properly in 3D space. At the 
	//same time we will attach a stencil buffer to our depth buffer. The stencil buffer 
	//can be used to achieve effects such as motion blur, volumetric shadows, and other things.
	
	D3D10_TEXTURE2D_DESC depthBufferDesc;

	// Initialize the description of the depth buffer.
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

	// Set up the description of the depth buffer.
	depthBufferDesc.Width = m_ScreenWidth;
	depthBufferDesc.Height = m_ScreenHeight;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc.Count = 1;
	depthBufferDesc.SampleDesc.Quality = 0;
	depthBufferDesc.Usage = D3D10_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;

	//Now we create the depth/stencil buffer using that description. You will notice 
	//we use the CreateTexture2D function to make the buffers, hence the buffer is 
	//just a 2D texture. The reason for this is that once your polygons are sorted 
	//and then rasterized they just end up being colored pixels in this 2D buffer. 
	//Then this 2D buffer is drawn to the screen.

	HRESULT result;

	// Create the texture for the depth buffer using the filled out description.
	result = m_Device->CreateTexture2D(&depthBufferDesc, NULL, &m_DepthStencilBuffer);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}

bool D3DObj::CreateStencilBuffer()
{
	//Now we need to setup the depth stencil description. This allows us to control
	//what type of depth test Direct3D will do for each pixel.

	D3D10_DEPTH_STENCIL_DESC depthStencilDesc;
	// Initialize the description of the stencil state.
	ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

	// Set up the description of the stencil state.
	depthStencilDesc.DepthEnable = true;
	depthStencilDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
	depthStencilDesc.DepthFunc = D3D10_COMPARISON_LESS;

	depthStencilDesc.StencilEnable = true;
	depthStencilDesc.StencilReadMask = 0xFF;
	depthStencilDesc.StencilWriteMask = 0xFF;

	// Stencil operations if pixel is front-facing.
	depthStencilDesc.FrontFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilDepthFailOp = D3D10_STENCIL_OP_INCR;
	depthStencilDesc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
	depthStencilDesc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;

	// Stencil operations if pixel is back-facing.
	depthStencilDesc.BackFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilDepthFailOp = D3D10_STENCIL_OP_DECR;
	depthStencilDesc.BackFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
	depthStencilDesc.BackFace.StencilFunc = D3D10_COMPARISON_ALWAYS;

	//With the description filled out we can now create a depth stencil state.

	HRESULT result;

	// Create the depth stencil state.
	result = m_Device->CreateDepthStencilState(&depthStencilDesc, &m_DepthStencilState);
	if(FAILED(result))
	{
		return false;
	}

	//With the created depth stencil state we can now set it on the Direct3D device so that it takes effect.

	// Set the depth stencil state on the D3D device.
	m_Device->OMSetDepthStencilState(m_DepthStencilState, 1);

	//The last thing we need to create is the description of the view of the depth stencil buffer. 
	//We do this so that Direct3D knows to use the depth buffer as a depth stencil texture. 
	//After filling out the description we then call the function CreateDepthStencilView 
	//via the Direct3D device to create it.

	D3D10_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	// Initailze the depth stencil view.
	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

	// Set up the depth stencil view description.
	depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthStencilViewDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;

	// Create the depth stencil view.
	result = m_Device->CreateDepthStencilView(m_DepthStencilBuffer, &depthStencilViewDesc, &m_DepthStencilView);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}

bool D3DObj::CreateRasterState()
{
	//Now that the render targets are setup we can continue on to some extra functions 
	//that will give us more control over our scenes for future tutorials. First thing 
	//is we'll create is a rasterizer state. This will give us control over how polygons 
	//are rendered. We can do things like make our scenes render in wireframe mode or have 
	//DirectX draw both the front and back faces of polygons. By default DirectX already 
	//has a rasterizer state set up and working the exact same as the one below but you 
	//have no control to change it unless you set up one yourself.

	D3D10_RASTERIZER_DESC rasterDesc;
	// Setup the raster description which will determine how and what polygons will be drawn.
	rasterDesc.AntialiasedLineEnable = false;
	//rasterDesc.CullMode = D3D10_CULL_BACK;
	rasterDesc.CullMode = D3D10_CULL_NONE;
	rasterDesc.DepthBias = 0;
	rasterDesc.DepthBiasClamp = 0.0f;
	rasterDesc.DepthClipEnable = true;
	rasterDesc.FillMode = D3D10_FILL_SOLID;
	rasterDesc.FrontCounterClockwise = false;
	rasterDesc.MultisampleEnable = false;
	rasterDesc.ScissorEnable = false;
	rasterDesc.SlopeScaledDepthBias = 0.0f;

	HRESULT result;

	// Create the rasterizer state from the description we just filled out.
	result = m_Device->CreateRasterizerState(&rasterDesc, &m_RasterState);
	if(FAILED(result))
	{
		return false;
	}

	// Now set the rasterizer state.
	m_Device->RSSetState(m_RasterState);

	return true;
}

void D3DObj::BeginScene(float red, float green, float blue, float alpha)
{
	float color[4];

	// Setup the color to clear the buffer to.
	color[0] = red;
	color[1] = green;
	color[2] = blue;
	color[3] = alpha;

	// Clear the back buffer.
	m_Device->ClearRenderTargetView(m_RenderTargetView, color);
    
	// Clear the depth buffer.
	m_Device->ClearDepthStencilView(m_DepthStencilView, D3D10_CLEAR_DEPTH|D3D10_CLEAR_STENCIL, 1.0f, 0);

	return;
}

void D3DObj::EndScene()
{
	// Present the back buffer to the screen since rendering is complete.
	if(m_vsync_enabled)
	{
		// Lock to screen refresh rate.
		m_SwapChain->Present(1, 0);
	}
	else
	{
		// Present as fast as possible.
		m_SwapChain->Present(0, 0);
	}

	return;
}

ID3D10Device* D3DObj::GetDevice()
{
	return m_Device;
}

void D3DObj::Shutdown()
{
	// Before shutting down set to windowed mode or when you release the swap chain it will throw an exception.
	if(m_SwapChain)
	{
		m_SwapChain->SetFullscreenState(false, NULL);
	}

	if(m_RasterState)
	{
		m_RasterState->Release();
		m_RasterState = 0;
	}

	if(m_DepthStencilView)
	{
		m_DepthStencilView->Release();
		m_DepthStencilView = 0;
	}

	if(m_DepthStencilState)
	{
		m_DepthStencilState->Release();
		m_DepthStencilState = 0;
	}

	if(m_DepthStencilBuffer)
	{
		m_DepthStencilBuffer->Release();
		m_DepthStencilBuffer = 0;
	}

	if(m_RenderTargetView)
	{
		m_RenderTargetView->Release();
		m_RenderTargetView = 0;
	}

	if(m_SwapChain)
	{
		m_SwapChain->Release();
		m_SwapChain = 0;
	}

	if(m_Device)
	{
		m_Device->Release();
		m_Device = 0;
	}

	return;
}

void D3DObj::SetScreenWidth(int width)
{
	m_ScreenWidth = width;
}

void D3DObj::SetScreenHeight(int height)
{
	m_ScreenHeight = height;
}

//The last helper function returns by reference the name of the video card and the 
//amount of dedicated memory on the video card. Knowing the video card name and 
//amount of video memory can help in debugging on different configurations.

void D3DObj::GetVideoCardInfo(char* cardName, int& memory)
{
	strcpy_s(cardName, 128, m_videoCardDescription);
	memory = m_videoCardMemory;
	return;
}

